// ==UserScript==
// @name         生财有术-志愿者后台辅助插件
// @namespace    http://tampermonkey.net/
// @version      1.2.1
// @description  提供表格视图等辅助功能
// @author       MD(mdxue00)
// @match        https://scys.com/activity/task/volunteer?*
// @icon         https://scys.com/favicon.ico
// @grant        none
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_addStyle
// @run-at       document-end
// @require      https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      http://html2canvas.hertzen.com/dist/html2canvas.min.js
// @require      https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js
// @updateURL    https://gitee.com/mondyxue/scys.user.js/raw/master/volunteer.scys.user.js
// ==/UserScript==
(function () {
    'use strict';

    const MD = window.MD = {
        addStyle(styleText) {
            var styleElement = document.createElement('style');
            styleElement.setAttribute('type', 'text/css')
            styleElement.innerHTML = styleText;
            document.head.appendChild(styleElement);
        },
        copyText(text) {
            navigator.clipboard.writeText(text).then(res => MD.showToast('已复制：\n' + text)).catch(err => MD.showToast('复制失败：\n' + err))
        },
        showToast(message, duration = 1500) {
            document.body.insertAdjacentHTML('beforeend', `
                        <div style="
                            position: fixed;
                            top: 50%;
                            left: 50%;
                            transform: translateX(-50%);
                            padding: 10px 20px;
                            background: rgba(0,0,0,0.8);
                            color: #fff;
                            border-radius: 4px;
                            z-index: 9999;
                        ">${message}</div>
                    `);
            const toastElement = document.body.lastElementChild
            setTimeout(() => toastElement.remove(), duration);
        },
        createButton(name, onClick, onCreate) {
            const button = document.createElement('button');
            button.innerText = name;
            onClick && button.addEventListener('click', onClick);
            onCreate && onCreate(button)
            return button;
        },
        createHoverMenu({ element, onMouseEnter, onMouseLeave, buttons }) {
            var hoverbtnsElement
            element.addEventListener('mouseenter', () => {
                if (element.querySelector('.md-hoverbtns')) { return }

                hoverbtnsElement = document.createElement('div');
                hoverbtnsElement.className = 'md-hoverbtns';
                const data = onMouseEnter && onMouseEnter(element)
                buttons.forEach(({ name, onClick, onCreate }) => {
                    hoverbtnsElement.appendChild(MD.createButton(name, e => {
                        hoverbtnsElement.remove()
                        onClick && onClick(e, data)
                    }, btn => {
                        onCreate && onCreate(btn, data)
                    }
                    ));
                });
                element.appendChild(hoverbtnsElement);
            });
            element.addEventListener('mouseleave', () => {
                hoverbtnsElement && hoverbtnsElement.remove();
                onMouseLeave && onMouseLeave()
            });
        },
        createHoverEditor({ element, value, onSave }) {

            const editDiv = document.createElement('div');
            editDiv.id = 'edit';
            editDiv.className = 'md-hoverbtns';
            editDiv.style.top = '0';
            editDiv.style.left = '0';
            editDiv.style.width = element.clientWidth + 'px';

            const overlayerDiv = document.createElement('div');
            overlayerDiv.className = 'overlayer';
            overlayerDiv.addEventListener('click', () => {
                editDiv.remove();
            });

            const inputElement = document.createElement('input');
            inputElement.style.width = element.clientWidth + 'px';
            inputElement.style.height = element.clientHeight + 'px';
            inputElement.value = value;
            inputElement.focus();
            inputElement.addEventListener('click', e => e.stopPropagation());

            const saveBtn = document.createElement('button');
            saveBtn.textContent = '保存';
            saveBtn.addEventListener('click', () => {
                if (inputElement.value === value) {
                    editDiv.remove()
                } else {
                    onSave(inputElement.value, value) && editDiv.remove()
                }
            });

            editDiv.appendChild(overlayerDiv);
            editDiv.appendChild(inputElement);
            editDiv.appendChild(saveBtn);
            element.appendChild(editDiv);
        },
        createCheckboxPopup({ onCreatePopup, options, onMoveOption, onGetIndex }) {
            const popup = document.createElement('div');
            popup.id = 'md-popup';
            popup.style.width = '200px'
            popup.style.maxHeight = '700px'
            popup.style.overflowY = 'scroll'
            onCreatePopup && onCreatePopup(popup)

            popup.insertOption = popup.createOption = ({ key, onCreateOption, checked, onCheckChange }, index) => {
                const label = document.createElement('label');
                label.key = label.htmlFor = key

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.value = checkbox.id = key;
                checkbox.checked = checked;
                checkbox.addEventListener('change', () => {
                    onCheckChange && onCheckChange(checkbox.checked)
                })

                const upButton = document.createElement('button');
                upButton.innerHTML = ' ↑ ';
                upButton.addEventListener('click', () => moveOption(key, -1));

                const downButton = document.createElement('button');
                downButton.innerHTML = ' ↓ ';
                downButton.addEventListener('click', () => moveOption(key, 1));

                label.appendChild(upButton);
                label.appendChild(downButton);
                label.appendChild(checkbox);
                if (onCreateOption) {
                    label.appendChild(onCreateOption(index, label, popup))
                } else {
                    const span = document.createElement('span');
                    span.innerHTML = checkbox.value
                    label.appendChild(span)
                }
                popup.insertBefore(label, popup.children[index + 1]);
            }

            options.forEach(popup.createOption);

            function moveOption(key, direction) {
                const index = onGetIndex(key)
                const directionIndex = index + direction
                if (index !== -1 && directionIndex >= 0 && directionIndex < options.length) {
                    if (direction > 0) {
                        popup.insertBefore(popup.children[directionIndex], popup.children[index])
                    } else {
                        popup.insertBefore(popup.children[index], popup.children[directionIndex])
                    }
                    onMoveOption(index, directionIndex)
                }
            }

            const overlay = document.createElement('div');
            overlay.id = 'md-overlay';
            overlay.addEventListener('click', function () {
                document.body.removeChild(popup);
                document.body.removeChild(overlay);
            });

            document.body.appendChild(overlay);
            document.body.appendChild(popup);
        },
        saveValueToLocal(key, value) {
            localStorage.setItem(key, JSON.stringify(value));
        },
        getValueFromLocal(key, defVal) {
            var savedValue = localStorage.getItem(key);
            try {
                savedValue = JSON.parse(savedValue)
            } catch (e) { }
            return savedValue === null ? defVal : savedValue;
        },
        clearLocalValue(key) {
            if (key) {
                localStorage.removeItem(key)
            } else {
                localStorage.clear()
            }
        },
        waitForElement(selector, delay = 0) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    const targetNode = document.querySelector(selector);
                    if (targetNode) {
                        resolve(targetNode);
                    } else {
                        const observer = new MutationObserver((mutations) => {
                            mutations.forEach((mutation) => {
                                const targetNode = document.querySelector(selector);
                                if (targetNode) {
                                    observer.disconnect();
                                    resolve(targetNode);
                                }
                            });
                        });
                        observer.observe(document.documentElement, { childList: true, subtree: true });
                    }
                }, delay);
            });
        },
        downloadZipFiles(zipFilename, files) {
            MD.showToast(`正在生成文件：${zipFilename}`)
            const tasks = files.map(({ filename, url }) => {
                return new Promise((resolve, reject) => {
                    const xhr = new XMLHttpRequest();
                    xhr.open("GET", url, true);
                    xhr.responseType = "blob";
                    xhr.onload = () => xhr.status === 200 ? resolve({ filename, blob: xhr.response }) : reject(new Error("Failed to download image: " + url))
                    xhr.onerror = () => reject(new Error("Failed to download image: " + url));
                    xhr.send();
                });
            })
            return Promise.all(tasks)
                .then(blobs => {
                    const zip = new JSZip();
                    blobs.forEach(({ filename, blob }) => {
                        zip.file(filename, blob, { binary: true })
                    })
                    return zip.generateAsync({ type: "blob" });
                })
                .then(zipBlob => {
                    MD.showToast(`准备下载文件：${zipFilename}`);
                    saveAs(zipBlob, zipFilename)
                })
                .catch(error => console.error("Failed to create ZIP file:", error));
        }
    }

    const DATA = window.DATA = resetDATA()

    function resetDATA() {
        const DATA = {
            isContinueSailing: true,
            activityName: '',
            volunteerName: '',
            currentDay: 1,
            cardInfoArray: [],
            cardInfoParserMap: new Map(),
            localCardInfoMap: {
                map: new Map(),
                localKey(cardInfo, key) {
                    return `${cardInfo.key}!${key}`
                },
                set(cardInfo, key, value) {
                    const localKey = this.localKey(cardInfo, key);
                    this.map.set(localKey, value)
                    MD.saveValueToLocal(localKey, value)
                },
                get(cardInfo, key) {
                    const localKey = this.localKey(cardInfo, key);
                    var value = this.map.get(localKey)
                    if (value === undefined) {
                        value = MD.getValueFromLocal(localKey, null)
                        this.map.set(localKey, value)
                    }
                    return value
                },
                delete(cardInfo, key) {
                    const localKey = this.localKey(cardInfo, key);
                    this.map.delete(localKey)
                    MD.clearLocalValue(localKey)
                }
            },
            customColumnArray: [],
            cardbtnConfigArray: [],
            hiddenCardbtnArray: [],
            hiddenRowArray: [],
            hiddenColumnArray: [],
            columnOrderArray: [],
            rowOrderArray: [],
            uneditableColumnArray: ['星球编号', '星球昵称', '已打卡天数', '距上岸天数', '可打卡天数', '可休息天数', '是否已打卡', '航海期数'],
            displayMode: 'card',
            sort: {
                key: '默认顺序',
                order: '升序'
            }
        }
        return DATA
    }

    function initDatas() {
        resetDATA()

        DATA.volunteerName = document.querySelector('#app > div.content-mt > div.container > div.volunteer-header > div.left-box > div.name > span:nth-child(2)').textContent
        DATA.activityName = document.querySelector('#app > div.content-mt > div.container > div.volunteer-header > div.left-box > div.activity > span:nth-child(2)').textContent
        DATA.currentDay = parseInt((document.querySelector('#app > div.content-mt > div.container > div.volunteer-header > div.daily > div.dailyLeft.v-row > span')?.textContent.match(/\d+/) || [])[0]) || 22;

        const strValueParser = valueElement => valueElement ? valueElement.textContent : ""
        const intValueParser = valueElement => { const i = parseInt(strValueParser(valueElement)); return Number.isInteger(i) ? i : '' }
        const cellRender = (cardInfo, key) => cardInfo[key]

        DATA.customColumnArray = MD.getValueFromLocal('DATA.customColumnArray') || []

        DATA.cardInfoParserMap = new Map()
        Array.of(
            ['星球编号', 'div.header > div.left.v-row > div > div.groupNumber > span', (e, cardInfo) => { cardInfo.key = parseInt(strValueParser(e).substring(5)); return cardInfo.key; }],
            ['星球头像', 'div.header > div.left.v-row > img', e => e.src,
                (cardInfo, key) => `<img style="width: 28px;height: 28px;" src="${cardInfo[key]}"></img>`
            ],
            ['星球昵称', 'div.header > div.left.v-row > div > div.groupName > span'],
            ['微信昵称', 'div.middle > div:nth-child(1) > span:nth-child(2)'],
            ['微信号', 'div.middle > div:nth-child(2) > span:nth-child(2)'],
            ['手机号', 'div.middle > div:nth-child(3) > span:nth-child(2)'],
            ['群昵称', 'div.middle > div:nth-child(4) > span:nth-child(2)'],
            ['上船进度', 'div.middle > div:nth-child(5) > span:nth-child(2)'],
            ['备注', 'div.middle > div:nth-child(6) > div', e => { const v = strValueParser(e); return v.includes('输入备注') ? '' : v; },
                (cardInfo, key) => `<div class="break-line" style="height: 33px;">${cardInfo[key]}</div>`],
            ['已打卡天数', 'div.bottom.v-row > div.left > span:nth-child(1)', intValueParser,
                (cardInfo, key) => {
                    var style = 'color:'
                    if (cardInfo[key] >= 12) {
                        style += 'green'
                    } else {
                        var d = cardInfo['可打卡天数'] - cardInfo['距上岸天数']
                        style += (0 <= d && d <= 3) ? 'red' : '#999'
                    }
                    return `<span style="${style}">${cardInfo[key]}</span>`
                }
            ],
            ['距上岸天数', 'div.bottom.v-row > div.left > span:nth-child(1)', e => Math.max(0, 12 - intValueParser(e))],
            ['可打卡天数', 'div.header > div.right.v-row', e => Math.max(0, 21 - DATA.currentDay + (strValueParser(e).includes('已打卡') ? 0 : 1))],
            ['可休息天数', 'div.header > div.right.v-row', (e, cardInfo) => Math.max(0, cardInfo['可打卡天数'] - cardInfo['距上岸天数'])],
            ['是否已打卡', 'div.header > div.right.v-row', e => strValueParser(e).includes('已打卡') ? '是' : '否', (v, k) => v[k] === '是' ? '✔️' : '❌'],
            ['航海期数', 'div.middle > div:nth-child(4) > span:nth-child(2)', e => parseInt(strValueParser(e).split('-')[2].substring(3))],
            ['常住地', 'div.middle > div:nth-child(4) > span:nth-child(2)', e => strValueParser(e).split('-')[1]]
        ).concat(DATA.customColumnArray.map(v => [v])).forEach(a => {
            DATA.cardInfoParserMap.set(a[0], {
                selector: a[1],
                valueParser: a[2] ? a[2] : strValueParser,
                cellRender: a[3] ? a[3] : cellRender,
            })
        })

        DATA.cardInfoArray = Array.from(document.querySelector('#scroll > div.volunteer-list').children).map((cardElement, index) => {
            var cardInfo = { cardElement, '默认顺序': index }
            DATA.cardInfoParserMap.forEach(({ selector, valueParser }, key) => {
                const localValue = DATA.localCardInfoMap.get(cardInfo, key)
                cardInfo[key] = localValue === null ? (selector ? valueParser(cardElement.querySelector(selector), cardInfo, key) : '') : localValue
            })
            return cardInfo
        })

        DATA.hiddenCardbtnArray = MD.getValueFromLocal('DATA.hiddenCardbtnArray') || DATA.hiddenCardbtnArray
        DATA.hiddenRowArray = MD.getValueFromLocal('DATA.hiddenRowArray') || DATA.hiddenRowArray
        DATA.hiddenColumnArray = MD.getValueFromLocal('DATA.hiddenColumnArray') || DATA.hiddenColumnArray
        DATA.rowOrderArray = MD.getValueFromLocal('DATA.rowOrderArray')
        if (!DATA.rowOrderArray || DATA.rowOrderArray.length !== DATA.cardInfoArray.size || DATA.cardInfoArray.filter(v => !DATA.rowOrderArray.includes(v.key)).length > 0) {
            DATA.rowOrderArray = DATA.cardInfoArray.map(v => v.key)
        } else {
            DATA.cardInfoArray.sort((a, b) => DATA.rowOrderArray.indexOf(a.key) > DATA.rowOrderArray.indexOf(b.key) ? 1 : -1)
        }
        DATA.columnOrderArray = MD.getValueFromLocal('DATA.columnOrderArray')
        if (!DATA.columnOrderArray || DATA.columnOrderArray.length !== DATA.cardInfoParserMap.size || Array.from(DATA.cardInfoParserMap.keys()).filter(k => !DATA.columnOrderArray.includes(k)).length > 0) {
            DATA.columnOrderArray = Array.from(DATA.cardInfoParserMap.keys())
        }
        DATA.displayMode = MD.getValueFromLocal('DATA.displayMode') || DATA.displayMode

    }

    function initStyles() {

        MD.addStyle(`
                .md-custombtn{
                    background: #36a590;
                    padding: 5px;
                    margin: 2.5px;
                    color: white;
                }
                .md-custombtn:hover {
                    background: #26816f;  /* 鼠标经过时的背景颜色 */
                    cursor: pointer;  /* 鼠标经过时的光标样式 */
                }
                .md-custombtn:active {
                    background: #1c6050;  /* 按钮按下时的背景颜色 */
                    outline: none;  /* 去除按钮按下时的默认轮廓线 */
                }
                .md-custombtn-sort{
                    background: #36a590;
                    padding: 4.3px;
                    margin: 2.5px;
                    color: white;
                    display: flex;
                    flex-direction: row;
                }
            `)
        MD.addStyle(`
                .md-cardbtns{
                    display: flex;
                    flex-direction:row;
                    flex-wrap:wrap;
                    margin: -15px 0 0 0;
                    padding:0 10px 5px 10px;
                }
                .md-cardbtn{
                    background: #36a590;
                    color: white;
                    font-size: 12px;
                    padding: 2.5px 5px;
                    margin: 2.5px;
                    height: fit-content;
                }
                .md-cardbtn:hover {
                    background: #26816f;
                    cursor: pointer;
                }
                .md-cardbtn:active {
                    background: #1c6050;
                    outline: none;
                }
                .md-cardbtn-img{
                    height: 20px;
                    width: 20px;
                    margin: 2.5px 5px;
                }
            `)
        MD.addStyle(`
                #md-table-wrapper{
                    width: 100%;
                    height: 753px;
                    overflow-x: scroll;
                    overflow-y: scroll;
                }
                #md-table{
                    table-layout: auto;
                    vertical-align: middle!important;
                    width: fit-content;
                    border-spacing: 0;
                }
                #md-table>*>*>* {
                    white-space: nowrap;
                    padding: 0 5px;
                    border: 1px solid #eee;
                    position: relative;
                }
                #md-table>*>*>*>.break-line{
                    white-space: pre-line;
                    text-align: left;
                    width: max-content;
                    overflow: hidden;
                }
                .md-hoverbtns {
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    background: white;
                    z-index: 3;
                    font-weight: normal;
                    box-shadow: 0 0 5px 5px #eee;
                    display: flex;
                    flex-direction:column;
                }
                .md-hoverbtns>.overlayer{
                    width: 4000px;
                    height: 4000px;
                    position: absolute;
                    top: -1000px;
                    left: -1000px;
                    z-index: -1;
                    background: rgb(0,0,0,0.2);
                }
                .md-hoverbtns>button:hover{
                    background: #eee;
                    cursor: pointer;
                }
                .md-hoverbtns>button{
                    height: 33px;
                    min-width: 72px;
                    background: white;
                }
                .md-hoverbtns>input{
                    height: 33px;
                    min-width: 72px;
                    background: white;
                }
                #md-table>*>*>*:hover {
                    background: #eee;
                    cursor: pointer;
                }
                #md-table>thead>tr>th{
                    background:white;
                    height: 33px;
                }
            `)
        MD.addStyle(`
            #md-popup {
                position: fixed;
                width: 400px;
                display:flex;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background-color: #fff;
                padding: 20px;
                z-index: 9999;
                align-items:flex-start;
                flex-wrap: wrap;
            }
            #md-popup > label{
                display:flex;
                flex-direction:row;
                align-items:center;
                font-size: 14px;
            }
            #md-popup > label > .content{
                display: flex;
                flex-direction:row;
                align-items: center;
            }
            #md-popup > label > input[type=checkbox]{
                height: 20px;
                margin: 10px;
            }
            #md-popup > label > .content > input[type=text]{
                border: 1px solid #eee;
                width: 180px;
                height: 33px;
            }
            #md-popup > label > *>* >select, #md-popup > label > * >select{
                color: unset;
                border-color: #ddd;
                margin-right: 10px;
                height: 33px;
            }
            #md-popup > label > .content > textarea{
                width: 300px;
                height: 65px;
                margin: 10px;
                border: 1px solid #eee;
                padding: 10px;
            }
            #md-popup > button:hover,#md-popup > label > button:hover,#md-popup > label > *> *>button:hover{
                background:#eee;
                cursor: pointer;
            }
            #md-popup > label > span{
                line-height:30px;
            }
            #md-overlay {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.5);
                z-index: 9998;
            }
        `)
        const cardlist = document.querySelector('.volunteer-list')
        cardlist.style.height = "700px"
        cardlist.style.overflowY = "scroll"
        cardlist.parentElement.style.overflowY = 'unset'
    }

    function initCardBtns() {

        const copyTextBtnRender = (btnName, copyContent, cardInfo) => {
            var btn = document.createElement('div')
            btn.className = 'md-cardbtn'
            btn.innerHTML = btnName
            btn.addEventListener('click', () => {
                copyContent = copyContent.replace(new RegExp(`{志愿者昵称}`, 'gm'), DATA.volunteerName)
                copyContent = copyContent.replace(new RegExp(`{航海名称}`, 'gm'), DATA.activityName)
                copyContent = copyContent.replace(new RegExp(`{航海第几天}`, 'gm'), (1 <= DATA.currentDay && DATA.currentDay <= 21) ? DATA.currentDay : '')
                Array.from(DATA.cardInfoParserMap.keys()).forEach(key => {
                    copyContent = copyContent.replace(new RegExp(`{${key}}`, 'gm'), cardInfo[key])
                })
                MD.copyText(copyContent)
            })
            return btn
        }
        const imgBtnRender = (btnName, copyContent, cardInfo) => {
            var btn = document.createElement('img')
            btn.className = 'md-cardbtn-img'
            btn.alt = btnName
            btn.src = copyContent
            btn.addEventListener('click', () => {
                window.open(btn.src)
            })
            return btn
        }

        DATA.cardbtnConfigArray = MD.getValueFromLocal('DATA.cardbtnConfigArray')
        if (!DATA.cardbtnConfigArray) {
            DATA.cardbtnConfigArray = (DATA.isContinueSailing ? Array.of(
                ['1.1.3添加好友', `我是生财有术 {航海名称}航海的志愿者，你的报名审核通过了，我来邀你进群～`
                ],
                ['2.1.1介绍自己', `{微信昵称} 你好呀，我是 {航海名称}志愿者 {志愿者昵称}，来邀请你上船～

                                    咱们参加过 8 月航海，算是老航海人啦，具体的航海规则咱们就不说了，哈哈哈。

                                    这次由我来为你保驾护航啦，一起成功上岸哇，来，上船！`
                ],
                ['2.1.2开船时间', '10 月续航开船时间是 10 月 11 号（周三）晚上 8 点，视频号直播形式/群内图文开船仪式，记得预留时间哈。'
                ],
                ['2.1.3上船流程', `接下来我会邀请你进入航海群，在此之前需要你关注一下#公众号：生财有术服务号

                                    后续会提醒打卡、日志加精、好事推荐，都会通过服务号传达给你，通知要打开才可以收到消息哈。`
                ],
                ['3.1.1续航更新规则', `这次续航有些变化，我来【划重点】总结一下：

                                    1. 每篇日志需要提交行动证明，不做无意义的水日志行为哦
                                    2. 以周为单位，每周至少记录 4 篇日志，不达要求则无法退还保证金
                                    3. 积极、利他，主动在群里发声、互动，不做小透明

                                    这期续航计划，你可能会有收获：

                                    1. 升级版航线图：更进阶且清晰的目标和项目路径，加速找到行进方向
                                    2. 不定期群内分享: 更加高频的图文分享，学习同行战友的实战技巧`
                ],
                ['4.1.2邀请进群', `点击下面的链接就可以进群啦。

                                    为了大家交流、链接更��便，需要你在【航海大群】按照格式：昵称-地区-总参与航海期数，更新你的群昵称。

                                    你的群昵称是：{群昵称}

                                    这是后台根据你往期参与的航海数据，生成的群昵称，你也可以自己再修改【昵称】和【地区】

                                    改完后可以拍拍我哈`
                ],
                ['5.1.2确认是否分享', `看到你报名时有填写【我愿意接受挑战，来分享我的实战经验】，我来和你确认下～

                                    想跟你再次确认下，是确定给大家分享的嘛？`
                ],
                ['5.1.2确认分享', `好，我会给你备注【确认分享】，后面会有另一位志愿者和你对接分享的具体事宜~

                                    开船7天后，会给你退还保证金（不用打卡）`
                ],
                ['5.1.2不作分享', `哈哈哈哈好呀，期待和你一起的 21 天航行，接下来我们一起加油哇[加油]

                                    这 21 天内还是要每周完成 4 次打卡哈，这样就可以拿回保证金啦。

                                    航海期间，我也会督促你完成航行日志的～`
                ],
                ['5.1.2未回复', `感觉到你主业的繁忙了哈哈哈，我先帮你备注成【确认不分享】哈。

                                虽然不分享了，但如果你后续完成每周 4 次的打卡，还是可以退还保证金的哈。

                                要是想深度参与的话，可以随时告诉我的，祝你航行愉快～`
                ]
            ) : Array.of(
                ['1.1添加好友', `{星球昵称} 你好呀！我是生财{航海名称}航海的志愿者 {志愿者昵称}，你的报名审核通过了，我来邀你进群~`
                ],
                ['3.1.1介绍自己', `{星球昵称} 你好呀，我是{航海名称}航海的志愿者 {志愿者昵称}，来邀请你上船~

                                    了解到你是第一次参加航海，我大概讲一下航海的相关内容哈。

                                    航海以自助实战为主，生财提供项目手册，如果你有不懂的可以在航海群 #举手 提问，会有专业的教练进行答疑。

                                    另外，航海期间我也会提醒你完成至少12次打卡，有任何问题也可以随时联系我。

                                    愿你拥有一段超棒的航海之旅~`
                ],
                ['3.1.1介绍自己（老船员）', `{星球昵称} 你好呀，我是{航海名称}航海的志愿者 {志愿者昵称}，来邀请你上船~

                                            看到你之前也参加过航海，算是老航海人啦，具体的航海规则咱们就不说了，哈哈哈。

                                            这次由我来为你保驾护航啦，一起成功上岸哇，来，上船！`
                ],
                ['3.1.2开船时间', '8月开船时间是9月4号（下周一）晚上8点，视频号直播形式，记得预留时间哈。'
                ],
                ['3.1.3上船流程', `在上船前，有三件事情需要我们一起协助完成，预计会花费你 5 分钟左右的时间~
                                    1. 确认生财有术航海公约
                                    2. 邀请你进入航海群
                                    3. 关注 #公众号：生财有术服务号
                                    （记得打开通知哦，主要用于提醒打卡）`],
                ['3.1.3上船流程（老船员）', `接下来我会邀请你进入航海群，在此之前需要你关注一下#公众号：生财有���服务号
                                            后续会提醒打卡、日志加精、好事推荐，都会通过服务号传达给你，通知要打开才可以收到消息哈。`
                ],
                ['4.1确认公约', '第一件事：确认航海公约，请逐一阅读公约信息，不要遗漏了哈~'
                ],
                ['4.1航海公约图片', 'https://cdnjson.com/images/2023/08/31/1693444632602.png', 'img'
                ],
                ['4.2公约重点', `我来【划重点】总结一下航海公约：

                        1. 使用生财有术星球绑定的微信参与打卡；
                        2. 航海21天需要打卡至少12次，每周建议完成4次打卡；
                        3. 每天1次打卡机会，错过不能补卡，不满12次不予退还保证金；
                        4. 每天会有【精选日志】可以来“抄作业”，学习实战思路；
                        5. 每周二、四有【高手分享】，提供问答和群聊精华；
                        6. 在群内提问时，具体描述问题，加上 #举手 标签，方便教练定位并解答问题。

                        如果以上确认无误，可以回复我【确认】，确认后我会邀请你上船~`
                ],
                ['5.2介绍群', `接下来我会邀请你进入两个群哈，邀请之前我先来简单介绍一下~

                    【航海大群】是航海的主要阵地，是大家交流、答疑、分享、链接的地方
                    【船员小组群】这个群是咱们小组的通知群，主要用于重要信息同步`
                ],
                ['5.3邀请进群', `点击群邀请链接就可以进群啦。

                        为了大家交流、链接更方便，需要你在【航海大群】按照格式：昵称-地区-Lv第几期航海，更新你的群昵称。

                        你的群昵称是：
                        {群昵称}

                        改完后可以拍拍我哈~`
                ]
            )).map(a => {
                a[1] = a[1].split('\n').map(t => t.trim()).join('\n')
                return a
            })
        }
        const cardbtnConfigArray = DATA.cardbtnConfigArray.map(a => ({ btnName: a[0], copyContent: a[1], btnRender: a[2] === 'img' ? imgBtnRender : copyTextBtnRender }))

        DATA.cardInfoArray.forEach(cardInfo => {
            if (!cardInfo.cardbtnsELement) {
                cardInfo.cardbtnsELement = cardInfo.cardElement.querySelector('.md-cardbtns')
            }
            if (!cardInfo.cardbtnsELement) {
                cardInfo.cardbtnsELement = document.createElement('td')
                cardInfo.cardbtnsELement.className = 'md-cardbtns'
                cardInfo.cardElement.style.maxHeight = 'unset'
                cardInfo.cardElement.appendChild(cardInfo.cardbtnsELement)
            }
            const cardbtnsELement = cardInfo.cardbtnsELement
            cardbtnsELement.innerHTML = ''
            cardbtnConfigArray.forEach(({ btnName, copyContent, btnRender }) => {
                const btn = btnRender(btnName, copyContent, cardInfo)
                btn.hidden = DATA.hiddenCardbtnArray.includes(btnName)
                cardbtnsELement.appendChild(btn)
            })
        });
    }

    function initCustomBtns() {

        const tabsBarElement = document.querySelector('#scroll > div.tabs > div')

        const defBtnRender = (btnName, onBtnClick) => {
            var btn = document.createElement('div')
            btn.id = `md-custombtn-${btnName}`
            btn.className = 'item'
            btn.innerHTML = `<div class="md-custombtn">${btnName}</div>`
            btn.addEventListener('click', () => {
                onBtnClick(btn.children[0])
            })
            return btn
        }

        Array.of(
            ["切换表格视图", btnElement => {
                switchDisplayMode(DATA.displayMode === 'hidden' ? 'hidden' : (DATA.displayMode === 'card' ? 'table' : 'card'))
            }],
            ["行设置", btnElement => {
                MD.createCheckboxPopup({
                    options: DATA.cardInfoArray.map((cardInfo) => {
                        const key = cardInfo.key
                        const checked = !DATA.hiddenRowArray.includes(key);
                        return {
                            key, checked,
                            onCreateOption: index => {
                                const span = document.createElement('span');
                                span.innerHTML = `${index + 1} ${cardInfo['星球昵称']}`
                                return span
                            },
                            onCheckChange: checked => {
                                if (checked) {
                                    DATA.hiddenRowArray = DATA.hiddenRowArray.filter(v => v !== key)
                                } else {
                                    DATA.hiddenRowArray.push(key)
                                }
                                updateViews()
                            }
                        }
                    }),
                    onMoveOption: (index, directionIndex) => {
                        var temp = DATA.rowOrderArray[index];
                        DATA.rowOrderArray[index] = DATA.rowOrderArray[directionIndex];
                        DATA.rowOrderArray[directionIndex] = temp;
                        updateViews()
                    },
                    onGetIndex: key => DATA.rowOrderArray.indexOf(key)
                })
            }],
            ["列设置", btnElement => {
                MD.createCheckboxPopup({
                    options: DATA.columnOrderArray.map(key => {
                        const checked = !DATA.hiddenColumnArray.includes(key);
                        return {
                            key, checked,
                            onCheckChange: checked => {
                                if (checked) {
                                    DATA.hiddenColumnArray = DATA.hiddenColumnArray.filter(v => v !== key)
                                } else {
                                    DATA.hiddenColumnArray.push(key)
                                }
                                updateViews()
                            }
                        }
                    }),
                    onMoveOption: (index, directionIndex) => {
                        var temp = DATA.columnOrderArray[index];
                        DATA.columnOrderArray[index] = DATA.columnOrderArray[directionIndex];
                        DATA.columnOrderArray[directionIndex] = temp;
                        updateViews()
                    },
                    onGetIndex: key => DATA.columnOrderArray.indexOf(key)
                })
            }],
            ["话术按钮设置", btnElement => {
                function updateCardbtns() {
                    MD.saveValueToLocal('DATA.cardbtnConfigArray', DATA.cardbtnConfigArray)
                    initCardBtns()
                    MD.showToast('按钮已更新')
                }

                MD.createCheckboxPopup({
                    onCreatePopup: popup => {
                        popup.style.width = '880px'

                        // 创建顶部按钮容器
                        const topBtnDiv = document.createElement('div')
                        topBtnDiv.style.display = 'flex'
                        topBtnDiv.style.justifyContent = 'flex-end'
                        topBtnDiv.style.marginBottom = '10px'

                        // 导出按钮
                        const exportBtn = document.createElement('button')
                        exportBtn.classList.add('md-custombtn')
                        exportBtn.innerHTML = '导出所有话术'
                        exportBtn.style.marginRight = '10px'
                        exportBtn.addEventListener('click', () => {
                            const wb = XLSX.utils.book_new()
                            const ws = XLSX.utils.json_to_sheet(DATA.cardbtnConfigArray.map(([name, content, type]) => ({
                                按钮名称: name,
                                话术内容: content,
                                按钮类型: type
                            })))
                            XLSX.utils.book_append_sheet(wb, ws, "话术配置")
                            XLSX.writeFile(wb, "话术配置.xlsx")
                        })
                        topBtnDiv.appendChild(exportBtn)

                        // 导入按钮
                        const importBtn = document.createElement('button')
                        importBtn.classList.add('md-custombtn')
                        importBtn.innerHTML = '导入所有话术'
                        importBtn.addEventListener('click', () => {
                            const input = document.createElement('input')
                            input.type = 'file'
                            input.accept = '.xlsx'
                            input.onchange = e => {
                                const file = e.target.files[0]
                                const reader = new FileReader()
                                reader.onload = e => {
                                    const data = new Uint8Array(e.target.result)
                                    const wb = XLSX.read(data, { type: 'array' })
                                    const ws = wb.Sheets[wb.SheetNames[0]]
                                    const json = XLSX.utils.sheet_to_json(ws)
                                    DATA.cardbtnConfigArray = json.map(row => [
                                        row['按钮名称'],
                                        row['话术内容'],
                                        row['按钮类型'] || 'text'
                                    ])
                                    updateCardbtns()
                                    popup.remove()
                                    document.querySelector('#md-overlay').remove()
                                    MD.showToast('话术导入成功')
                                }
                                reader.readAsArrayBuffer(file)
                            }
                            input.click()
                        })
                        topBtnDiv.appendChild(importBtn)

                        // 将按钮容器插入到弹窗顶部
                        popup.insertBefore(topBtnDiv, popup.firstChild)
                    },
                    options: DATA.cardbtnConfigArray.map(toOption),
                    onMoveOption: (index, directionIndex) => {
                        var temp = DATA.cardbtnConfigArray[index];
                        DATA.cardbtnConfigArray[index] = DATA.cardbtnConfigArray[directionIndex];
                        DATA.cardbtnConfigArray[directionIndex] = temp;
                        updateCardbtns()
                    },
                    onGetIndex: key => DATA.cardbtnConfigArray.findIndex(v => v[0] === key)
                })

                function toOption(cardbtnConfig) {
                    const key = cardbtnConfig[0]
                    const checked = !DATA.hiddenCardbtnArray.includes(key);
                    return {
                        key, checked,
                        onCheckChange: checked => {
                            if (checked) {
                                DATA.hiddenCardbtnArray = DATA.hiddenCardbtnArray.filter(v => v !== key)
                            } else {
                                DATA.hiddenCardbtnArray.push(key)
                            }
                            updateCardbtns()
                        },
                        onCreateOption: (index, label, popup) => {
                            const contentDiv = document.createElement('div')
                            contentDiv.setAttribute('class', 'content')

                            // 删除按钮
                            const deleteBtn = document.createElement('button')
                            deleteBtn.innerHTML = '删除'
                            deleteBtn.style.marginRight = '10px'
                            deleteBtn.addEventListener('click', () => {
                                if (confirm(`确定要删除按钮"${key}"吗?`)) {
                                    DATA.cardbtnConfigArray = DATA.cardbtnConfigArray.filter(v => v[0] !== key)
                                    label.remove()
                                    updateCardbtns()
                                }
                            })
                            contentDiv.appendChild(deleteBtn)

                            const keyInput = document.createElement('input');
                            keyInput.value = key
                            keyInput.type = 'text'

                            const typeSelect = document.createElement('select');
                            const textOption = document.createElement('option');
                            textOption.value = 'text';
                            textOption.innerText = '文字按钮';
                            textOption.selected = cardbtnConfig[2] !== 'img'
                            const imgOption = document.createElement('option');
                            imgOption.value = 'img';
                            imgOption.innerText = '图片按钮';
                            imgOption.selected = cardbtnConfig[2] === 'img';
                            typeSelect.appendChild(textOption);
                            typeSelect.appendChild(imgOption);

                            const contentTextarea = document.createElement('textarea');
                            contentTextarea.value = cardbtnConfig[1];

                            var savedCursorPosition = -1;
                            contentTextarea.addEventListener('click', function (e) {
                                savedCursorPosition = contentTextarea.selectionStart;
                            });

                            const keySelect = document.createElement('select')
                            Array.of('选择要插入的数据', '航海名称', '志愿者昵称', '航海第几天').concat(DATA.columnOrderArray).forEach(key => {
                                const option = document.createElement('option')
                                option.text = key === '选择要插入的数据' ? key : `插入：${key}`
                                option.value = key
                                keySelect.appendChild(option)
                            })
                            keySelect.addEventListener('change', () => {
                                const text = `{${keySelect.selectedOptions[0].value}}`
                                if (savedCursorPosition < 0) {
                                    contentTextarea.value += text;
                                } else {
                                    let originalText = contentTextarea.value;
                                    let updatedText = originalText.substring(0, savedCursorPosition) + text + originalText.substring(savedCursorPosition);
                                    contentTextarea.value = updatedText;
                                }
                                keySelect.selectedIndex = 0
                            })

                            const saveButton = document.createElement('button');
                            saveButton.innerHTML = '保存设置';
                            saveButton.style.width = '150px'
                            saveButton.style.padding = '5px'
                            saveButton.addEventListener('click', () => {
                                cardbtnConfig[0] = keyInput.value
                                cardbtnConfig[1] = contentTextarea.value
                                cardbtnConfig[2] = typeSelect.selectedOptions[0].value
                                updateCardbtns()
                            });

                            const newItemButton = document.createElement('button');
                            newItemButton.innerHTML = '新增按钮';
                            newItemButton.style.width = '150px'
                            newItemButton.style.padding = '5px'
                            newItemButton.addEventListener('click', () => {
                                const index = DATA.cardbtnConfigArray.findIndex(v => v[0] === cardbtnConfig[0]);
                                var name = `按钮${1}`
                                for (let i = 1; DATA.cardbtnConfigArray.findIndex(v => v[0] === name) !== -1; i++) {
                                    name = `按钮${i}`
                                }
                                const newConfig = [name, '', 'text'] // 初始化空内容
                                DATA.cardbtnConfigArray.splice(index + 1, 0, newConfig)
                                popup.insertOption(toOption(newConfig), index)
                                updateCardbtns()
                            });

                            const btnDiv = document.createElement('div')
                            btnDiv.setAttribute('style', 'display:flex;flex-direction:column;')
                            btnDiv.appendChild(keySelect)
                            btnDiv.appendChild(saveButton)
                            btnDiv.appendChild(newItemButton)

                            contentDiv.appendChild(typeSelect);
                            contentDiv.appendChild(keyInput);
                            contentDiv.appendChild(contentTextarea);
                            contentDiv.appendChild(btnDiv);
                            return contentDiv
                        }
                    }
                }
            }],
            ["生成全家福", btnElement => {

                const popup = document.createElement('div');
                popup.id = 'md-popup';

                const overlay = document.createElement('div');
                overlay.id = 'md-overlay';
                const { activityName, volunteerName } = DATA
                popup.innerHTML = `
                        <div id="id_members" style="background: transparent;position: relative;"></div>
                        <button style="width:100%;padding: 20px;">保存【${activityName}丨${volunteerName}组】全家福</button>
                    `;

                overlay.addEventListener('click', function () {
                    document.body.removeChild(popup);
                    document.body.removeChild(overlay);
                });

                document.body.appendChild(popup);
                document.body.appendChild(overlay);

                const avatasContainer = popup.firstElementChild;
                var avatars = Array.of(
                    document.querySelector('#app > div.top-nav > div > div.inline-block.popper-me > div:nth-child(1) > div > img').src
                ).concat(
                    DATA.cardInfoArray.filter(v => !v.trElement.hidden).map(v => v['星球头像'])
                ).map(imgUrl => {
                    var img = document.createElement('img')
                    img.src = imgUrl
                    return img
                })

                const size = 50;
                var startIndex = 0
                var overLen = Math.max(avatars.length - 1 - 35, 0)
                popup.style.width = avatasContainer.style.width = avatasContainer.style.height = `${7 * size + 8 * 5}px`
                Array.of(1, 6, 11 + Math.min(2, overLen), 18 + (overLen > 2 ? overLen - 2 : 0)).forEach((len, layoutIndex) => {
                    if (startIndex >= avatars.length) { return }

                    var endIndex = startIndex + len
                    var subAvatars = avatars.slice(startIndex, endIndex)
                    var radius = (size + 5) * layoutIndex
                    const angle = (2 * Math.PI) / subAvatars.length;
                    for (let i = 0; i < subAvatars.length; i++) {
                        const x = radius * Math.cos(i * angle);
                        const y = radius * Math.sin(i * angle);
                        const img = subAvatars[i];
                        img.style = "";
                        img.className = "";
                        img.style.position = "absolute";
                        img.style.width = size + "px";
                        img.style.height = size + "px";
                        img.style.borderRadius = '50%'
                        img.style.left = (avatasContainer.clientWidth - size / 3) / 2 + x - size / Math.PI + "px";
                        img.style.top = (avatasContainer.clientHeight - size / 3) / 2 + y - size / Math.PI + "px";
                        avatasContainer.appendChild(img);
                    }
                    startIndex = endIndex
                })

                popup.style.scale = ''

                popup.lastElementChild.addEventListener('click', () => {
                    html2canvas(avatasContainer, { backgroundColor: 'transparent', scale: 4 }).then(canvas => {
                        canvas.toBlob(blob => {
                            const a = document.createElement('a');
                            a.download = `${activityName}-${volunteerName}-全家福（透明底）.png`;
                            a.href = URL.createObjectURL(blob);
                            a.click();
                        }, 'image/png');
                    });
                })

            }]
        ).map(a => ({ btnRender: a[2] ? a[2] : defBtnRender, btnName: a[0], onBtnClick: a[1] })).forEach(({ btnName, btnRender, onBtnClick }) => tabsBarElement.appendChild(btnRender(btnName, onBtnClick)))
    }

    function initTableView() {
        if (!document.querySelector('#md-table')) {
            document.querySelector('#scroll').insertAdjacentHTML('beforeend', `
                <div id="md-table-wrapper">
                    <table id="md-table">
                        <thead></thead><tbody></tbody>
                    </table>
                </div>
            `)
        }

        document.querySelector('#md-table').parentElement.hidden = true

        const thead = document.querySelector('#md-table > thead')
        thead.innerHTML = ''
        const trElement = document.createElement('tr')
        DATA.columnOrderArray.forEach((key) => {
            const columnThElement = document.createElement('th')
            columnThElement.key = key
            columnThElement.innerHTML = key

            MD.createHoverMenu({
                element: columnThElement,
                onMouseEnter: () => ({
                    index: DATA.columnOrderArray.indexOf(key),
                    order: columnThElement.order || '升序'
                }),
                buttons: Array.of(
                    ['复制列数据', () => MD.copyText(DATA.cardInfoArray.filter(v => !DATA.hiddenRowArray.includes(v.key)).map(v => v[key]).join('\n'))],
                    [`按列排序`, (e, { order }) => {
                        DATA.rowOrderArray.sort((a, b) => {
                            a = DATA.cardInfoArray.find(v => v.key === a)[key];
                            b = DATA.cardInfoArray.find(v => v.key === b)[key];
                            return a === b ? 0 : ((order === '升序' ? a < b : a > b) ? -1 : 1);
                        });
                        columnThElement.order = order === '升序' ? '降序' : '升序';
                        updateViews();
                    }, (btn, { order }) => {
                        btn.innerText = `按列${order}排序`
                    }],
                    ['左移一列', (e, { index }) => {
                        let nextIndex = -1;
                        for (let i = index - 1; i >= 0; i--) {
                            if (!DATA.hiddenColumnArray.includes(DATA.columnOrderArray[i])) {
                                nextIndex = i;
                                break;
                            }
                        }
                        if (nextIndex !== -1) {
                            const temp = DATA.columnOrderArray[index];
                            DATA.columnOrderArray[index] = DATA.columnOrderArray[nextIndex];
                            DATA.columnOrderArray[nextIndex] = temp;
                            updateViews();
                        }
                    }, (btn, { index }) => { btn.hidden = index - 1 < 0 }],
                    ['右移一列', (e, { index }) => {
                        let nextIndex = -1;
                        for (let i = index + 1; i < DATA.columnOrderArray.length; i++) {
                            if (!DATA.hiddenColumnArray.includes(DATA.columnOrderArray[i])) {
                                nextIndex = i;
                                break;
                            }
                        }
                        if (nextIndex !== -1) {
                            const temp = DATA.columnOrderArray[index];
                            DATA.columnOrderArray[index] = DATA.columnOrderArray[nextIndex];
                            DATA.columnOrderArray[nextIndex] = temp;
                            updateViews();
                        }
                    }, (btn, { index }) => { btn.hidden = index + 1 >= DATA.columnOrderArray.length }],
                    ['隐藏列数据', () => {
                        DATA.hiddenColumnArray.push(key);
                        updateViews();
                    }],
                    ['新增一列', (e, { index }) => {
                        var name = `列${1}`
                        for (let i = 1; DATA.columnOrderArray.includes(name); i++) {
                            name = `列${i}`
                        }
                        DATA.customColumnArray.push(name);
                        DATA.columnOrderArray.splice(index + 1, 0, name);
                        updateViews()
                        updateAll()
                    }],
                    ['复制一列', (e, { index }) => {
                        var key = DATA.columnOrderArray[index]
                        var name = `${key}${1}`
                        for (let i = 1; DATA.columnOrderArray.includes(name); i++) {
                            name = `${key}${i}`
                        }
                        DATA.customColumnArray.push(name);
                        DATA.columnOrderArray.splice(index + 1, 0, name);
                        DATA.cardInfoArray.forEach(cardInfo => {
                            DATA.localCardInfoMap.set(cardInfo, name, cardInfo[key])
                        })
                        updateViews()
                        updateAll()
                    }],
                    ['修改列名', (e, { index }) => {
                        MD.createHoverEditor({
                            element: columnThElement, value: DATA.columnOrderArray[index], onSave(value) {
                                DATA.cardInfoArray.forEach(cardInfo => {
                                    if (cardInfo[key] !== '') {
                                        DATA.localCardInfoMap.set(cardInfo, value, cardInfo[key])
                                    }
                                })
                                DATA.cardInfoArray.forEach(cardInfo => {
                                    DATA.localCardInfoMap.delete(cardInfo, key)
                                })
                                DATA.customColumnArray.splice(DATA.customColumnArray.indexOf(DATA.columnOrderArray[index]), 1, value);
                                DATA.columnOrderArray.splice(index, 1, value);
                                updateViews()
                                updateAll()
                            }
                        })
                    }, btn => { btn.hidden = !DATA.customColumnArray.includes(key) }],
                    ['删除列', (e, { index }) => {
                        DATA.customColumnArray.splice(DATA.customColumnArray.indexOf(DATA.columnOrderArray[index]), 1);
                        DATA.columnOrderArray.splice(index, 1);
                        DATA.cardInfoArray.forEach(cardInfo => {
                            DATA.localCardInfoMap.delete(cardInfo, key)
                        })
                        updateViews()
                        updateAll()
                    }, btn => { btn.hidden = !DATA.customColumnArray.includes(key) }]
                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
            });

            trElement.appendChild(columnThElement)
        })
        thead.appendChild(trElement)

        const tbody = document.querySelector('#md-table > tbody')
        tbody.innerHTML = ''
        DATA.cardInfoArray.forEach(cardInfo => {
            const trElement = document.createElement('tr')
            DATA.cardInfoParserMap.forEach(({ selector, cellRender }, key) => {
                const tdElement = document.createElement('td')
                tdElement.key = key
                tdElement.innerHTML = cellRender(cardInfo, key)
                if (DATA.localCardInfoMap.get(cardInfo, key) !== null) {
                    tdElement.style.fontWeight = '900'
                }

                var onCellDoubleClick
                if (!DATA.uneditableColumnArray.includes(key)) {
                    onCellDoubleClick = () => {
                        if (key === '上船进度' || key === '备注') {
                            cardInfo.cardElement.querySelector(selector).click()
                            setTimeout(() => {
                                const confirmBtn = document.querySelector('#app > div.modal > div > div > div.button-remark > div')
                                if (confirmBtn) {
                                    confirmBtn.addEventListener('click', () => {
                                        var totalTime = 0
                                        const interval = setInterval(() => {
                                            if (totalTime >= 5000 || !document.querySelector('#app > div.modal > div > div > div.button-remark > div')) {
                                                updateAll()
                                                clearInterval(interval)
                                            }
                                            totalTime += 1000
                                        }, 200);
                                    })
                                } else {
                                    MD.showToast('如果修改后，数据没更新，可手动刷新页面', 3000)
                                }
                            }, 200);
                        } else {
                            MD.createHoverEditor({
                                element: tdElement, value: cardInfo[key], onSave: (value) => {
                                    DATA.localCardInfoMap.set(cardInfo, key, value);
                                    updateAll();
                                    return true
                                }
                            })
                        }
                    }
                    tdElement.addEventListener('dblclick', onCellDoubleClick)
                }

                MD.createHoverMenu({
                    element: tdElement,
                    onMouseEnter: () => {
                        if (key === '备注') { tdElement.firstElementChild.style.height = 'unset' }
                    },
                    onMouseLeave: () => {
                        if (key === '备注') { tdElement.firstElementChild.style.height = '33px' }
                    },
                    buttons: Array.of(
                        ['复制', () => MD.copyText(cardInfo[key])],
                        ['编辑', onCellDoubleClick, (btn => { btn.hidden = !onCellDoubleClick })],
                        ['重置', () => {
                            DATA.localCardInfoMap.delete(cardInfo, key);
                            updateAll();
                        }, (btn => { btn.hidden = DATA.localCardInfoMap.get(cardInfo, key) === null })]
                    ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                });

                trElement.appendChild(tdElement)
            })
            cardInfo.trElement = trElement
            tbody.appendChild(trElement)
        })
    }

    function updateViews() {

        const trElement = document.querySelector('#md-table>thead>tr')
        if (!trElement.numberThElement) {
            const numberThElement = document.createElement('th')
            numberThElement.innerHTML = '行号'

            MD.createHoverMenu({
                element: numberThElement,
                buttons: Array.of(
                    ['复制行列数据', () => {
                        MD.copyText(DATA.cardInfoArray
                            .filter(cardInfo => !DATA.hiddenRowArray.includes(cardInfo.key))
                            .map(cardInfo => DATA.columnOrderArray.filter(key => !DATA.hiddenColumnArray.includes(key)).map(key => cardInfo[key]).join('\t'))
                            .join('\n'))
                    }],
                    ['重置行设置', () => {
                        DATA.rowOrderArray.sort((a, b) => DATA.cardInfoArray.find(v => v.key === a)['默认顺序'] > DATA.cardInfoArray.find(v => v.key === b)['默认顺序'] ? 1 : -1)
                        DATA.hiddenRowArray = []
                        updateViews();
                    }],
                    ['重置列设置', () => {
                        const keys = Array.from(DATA.cardInfoParserMap.keys())
                        DATA.columnOrderArray.sort((a, b) => {
                            return keys.indexOf(a) > keys.indexOf(b) ? 1 : -1
                        })
                        DATA.hiddenColumnArray = []
                        updateViews();
                    }],
                    ['重置话术按钮设置', () => {
                        DATA.cardbtnConfigArray = []
                        MD.clearLocalValue('DATA.cardbtnConfigArray')
                        initCardBtns()
                    }]
                ).map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
            });

            trElement.numberThElement = numberThElement
        }
        if (!trElement.optionThElement) {
            trElement.optionThElement = document.createElement('th')
            trElement.optionThElement.innerHTML = '操作'
        }

        const thElements = Array.from(trElement.children).sort((a, b) => DATA.columnOrderArray.indexOf(a.key) > DATA.columnOrderArray.indexOf(b.key) ? 1 : -1);

        trElement.append(trElement.numberThElement)

        thElements.forEach(numberThElement => {
            numberThElement.parentNode.append(numberThElement)
            if (DATA.hiddenColumnArray.includes(numberThElement.key)) {
                numberThElement.hidden = true
            } else {
                numberThElement.hidden = false
            }
        })

        trElement.append(trElement.optionThElement)

        DATA.cardInfoArray.sort((a, b) => DATA.rowOrderArray.indexOf(a.key) > DATA.rowOrderArray.indexOf(b.key) ? 1 : -1)
            .forEach((cardInfo, index) => {
                const { cardElement, trElement } = cardInfo
                cardElement.parentNode.append(cardElement)
                trElement.parentNode.append(trElement)

                if (DATA.hiddenRowArray.includes(cardInfo['星球编号'])) {
                    cardElement.style.display = 'none'
                    trElement.hidden = true
                } else {
                    cardElement.style.display = 'flex'
                    trElement.hidden = false

                    const tdElements = Array.from(trElement.children).sort((a, b) => DATA.columnOrderArray.indexOf(a.key) > DATA.columnOrderArray.indexOf(b.key) ? 1 : -1)

                    if (!trElement.numberTdElement) {
                        const numberTdElement = document.createElement('td')

                        MD.createHoverMenu({
                            element: numberTdElement,
                            onMouseEnter: () => ({
                                index: DATA.rowOrderArray.indexOf(cardInfo.key),
                            }),
                            buttons: Array.of(
                                ['复制行数据', () => MD.copyText(DATA.columnOrderArray.filter(key => !DATA.hiddenColumnArray.includes(key)).map(key => cardInfo[key]).join('\t'))],
                                ['上移一行', (e, { index }) => {
                                    var nextIndex = -1
                                    for (let i = index - 1; i > 0; i--) {
                                        if (!DATA.hiddenRowArray.includes(DATA.rowOrderArray[i])) {
                                            nextIndex = i
                                            break
                                        }
                                    }
                                    if (nextIndex != -1) {
                                        var temp = DATA.rowOrderArray[index];
                                        DATA.rowOrderArray[index] = DATA.rowOrderArray[nextIndex];
                                        DATA.rowOrderArray[nextIndex] = temp;
                                        updateViews();
                                    }
                                }, (btn, { index }) => {
                                    btn.hidden = index - 1 < 0
                                }],
                                ['下移一行', (e, { index }) => {
                                    var nextIndex = -1
                                    for (let i = index + 1; i < DATA.rowOrderArray.length; i++) {
                                        if (!DATA.hiddenRowArray.includes(DATA.rowOrderArray[i])) {
                                            nextIndex = i
                                            break
                                        }
                                    }
                                    if (nextIndex != -1) {
                                        var temp = DATA.rowOrderArray[index];
                                        DATA.rowOrderArray[index] = DATA.rowOrderArray[nextIndex];
                                        DATA.rowOrderArray[nextIndex] = temp;
                                        updateViews();
                                    }
                                }, (btn, { index }) => {
                                    btn.hidden = index + 1 >= DATA.rowOrderArray.length
                                }],
                                ['隐藏行数据', () => {
                                    DATA.hiddenRowArray.push(cardInfo['星球编号'])
                                    updateViews()
                                }])
                                .map(a => ({ name: a[0], onClick: a[1], onCreate: a[2] }))
                        });

                        trElement.numberTdElement = numberTdElement
                    }
                    trElement.numberTdElement.innerHTML = index + 1
                    trElement.append(trElement.numberTdElement)

                    tdElements.forEach(tdElement => {
                        tdElement.parentNode.append(tdElement)
                        if (DATA.hiddenColumnArray.includes(tdElement.key)) {
                            tdElement.hidden = true
                        } else {
                            tdElement.hidden = false
                        }
                    })
                }
            })

        if (DATA.displayMode === 'card') {
            DATA.cardInfoArray.forEach(({ cardElement, cardbtnsELement }) => {
                cardElement.style.marginRight = '10px'
                cardbtnsELement.style.height = 'unset'
                cardbtnsELement.style.alignItems = 'unset'
                cardbtnsELement.style.flexWrap = 'wrap'
                cardbtnsELement.style.marginTop = '-15px'
                cardElement.appendChild(cardbtnsELement)
            })
        } else {
            DATA.cardInfoArray.forEach(({ trElement, cardbtnsELement }) => {
                cardbtnsELement.style.height = '33px'
                cardbtnsELement.style.alignItems = 'center'
                cardbtnsELement.style.flexWrap = 'unset'
                cardbtnsELement.style.marginTop = '0px'
                trElement.appendChild(cardbtnsELement)
            })
        }

        MD.saveValueToLocal('DATA.hiddenRowArray', DATA.hiddenRowArray)
        MD.saveValueToLocal('DATA.hiddenColumnArray', DATA.hiddenColumnArray)
        MD.saveValueToLocal('DATA.columnOrderArray', DATA.columnOrderArray)
        MD.saveValueToLocal('DATA.rowOrderArray', DATA.rowOrderArray)
        MD.saveValueToLocal('DATA.customColumnArray', DATA.customColumnArray)

    }

    function switchDisplayMode(displayMode) {
        displayMode = displayMode = displayMode ? displayMode : DATA.displayMode

        const table = document.querySelector('#md-table')
        if (displayMode === 'hidden') {
            table.parentElement.hidden = true
            return
        }

        const btnElement = document.querySelector('#md-custombtn-切换表格视图>.md-custombtn')
        const cardlist = document.querySelector('.volunteer-list')
        if (displayMode === 'table') {
            DATA.displayMode = 'table'
            btnElement.innerHTML = '切换卡片视图'
            cardlist.style.display = 'none'
            cardlist.hidden = true
            table.parentElement.hidden = false
        } else {
            DATA.displayMode = 'card'
            btnElement.innerHTML = '切换表格视图'
            cardlist.style.display = 'flex'
            cardlist.hidden = false
            table.parentElement.hidden = true
        }
        MD.saveValueToLocal('DATA.displayMode', displayMode)
        updateViews()
    }

    function updateAll() {
        initDatas()
        initTableView()
        initCardBtns()
        updateViews()
        switchDisplayMode()
    }

    MD.waitForElement('.cards-volunteer').then(firstCardElement => {
        initStyles()
        initDatas()
        initTableView()
        initCardBtns()
        initCustomBtns()
        updateViews()
        switchDisplayMode()
    })

    MD.waitForElement('#scroll > div.tabs > div > div:nth-child(1)').then(tabElement => {
        tabElement.addEventListener('click', () => {
            MD.waitForElement('.cards-volunteer', 1000).then(firstCardElement => {
                if (!firstCardElement.querySelector('.md-cardbtns')) {
                    updateAll()
                }
            })
        })
        tabElement.nextElementSibling.addEventListener('click', () => switchDisplayMode('hidden'))
        tabElement.nextElementSibling.nextElementSibling.addEventListener('click', () => switchDisplayMode('hidden'))
    })

})();
